package com.sway.data.transation;

import com.sway.data.annotation.ManageTransactional;
import com.sway.data.pool.ConnectionPool;



import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class TransactionManager {
    
    public ConnectionPool connectionPool;
    public TransactionManager(ConnectionPool connectionPool){
        this.connectionPool = connectionPool;
    }

    @Pointcut("@annotation(com.sway.data.annotation.ManageTransactional)")
    public void pointcut(){}

    @Around("pointcut()")
    public Object manageTransactional(ProceedingJoinPoint pjp){
        Connection connection = connectionPool.getCurrentConnection().get();
        if (connection == null){
            connection = connectionPool.getConnection();
        }

        try {
            setAutoCommit(connection,false);

            Object[] args = pjp.getArgs();
            Object result = pjp.proceed(args);

            commitTransaction(connection);

            return result;
        } catch (Throwable e) {
            rollbackTransaction(connection,getAnnotationValue(pjp),e);
        }finally {
            closeConnection(connection);
        }
        return null;
    }

    private void setAutoCommit(Connection connection, boolean b) throws SQLException {
        connection.setAutoCommit(b);
    }

    private void commitTransaction(Connection connection) {
        try {
            connection.commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    private void closeConnection(Connection connection) {
        try {
            setAutoCommit(connection,true);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    private void rollbackTransaction(Connection connection, Class<?>[] exceptions, Throwable e) {
        for (Class<?> exception : exceptions) {
            if (exception.isAssignableFrom(e.getClass())){
                try {
                    connection.rollback();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        e.printStackTrace();
    }

    private Class<?>[] getAnnotationValue(ProceedingJoinPoint pjp){
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String methodName = signature.getName();
        Class[] parameterTypes = signature.getParameterTypes();
        Method method = null;
        try {
            method = pjp.getTarget().getClass().getDeclaredMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        ManageTransactional annotation = method.getAnnotation(ManageTransactional.class);
        Class<?>[] exception = annotation.exception();
        return exception;
    }

}
